with Legend.Grps;
with Legend.Stages;
with Legend.Traces;
with Legend.Menus;
with Legend.StrRes;
with Legend.Dialogs;
with Legend.Resources;
with Legend.Stages.Rooms;
with Legend.Controls;
with Legend.Colors;
with Legend.Renders;
with Legend.Music;
with SDL.Keysym;
with Storage_Streams; use Storage_Streams;
with Storage_Streams.Unbounded;
with Ada.Containers.Doubly_Linked_Lists;
with Ada.Calendar; use Ada.Calendar;
package body Legend.Story_Engine is

   Debug_Mode : Boolean := False;

   procedure Toggle_Debug_Mode
   is
   begin
      Debug_Mode := not Debug_Mode;
   end Toggle_Debug_Mode;

   -- 当前脚本流
   Script : aliased Unbounded.Unbounded_Stream_Type;

   function Read_Uint16 return Uint16_t
   is
   begin
      return Uint16_t'Input(Script'Access);
   end Read_Uint16;

   function Read_Int16 return Int16_t
   is
   begin
      return Int16_t'Input(Script'Access);
   end Read_Int16;

   function Read_Uint16_Array (Count : Natural) return Uint16_Array
   is
      Result : Uint16_Array(1 .. Count);
   begin
      Uint16_Array'Read(Script'Access, Result);
      return Result;
   end Read_Uint16_Array;

   function Read_Point return Point_Type
   is
   begin
      return Point_Type'Input(Script'Access);
   end Read_Point;

   function Read_Story return Story_Type
   is
   begin
      return Story_Type'Input(Script'Access);
   end Read_Story;

   function Read_Scene_Story_Id return Scene_Story_Id
   is
   begin
      return Scene_Story_Id'Input(Script'Access);
   end Read_Scene_Story_Id;

   function Read_Boolean return Boolean
   is
   begin
      return Read_Uint16 /= 0;
   end Read_Boolean;

   function Read_Map_Layer return Map_Layer_Type
   is
   begin
      return Map_Layer_Type'Input(Script'Access);
   end Read_Map_Layer;

   function Read_Sex return Sex_Type
   is
   begin
      return Sex_Type'Input(Script'Access);
   end Read_Sex;

   function Read_Face return Face_Type
   is
   begin
      return Face_Type'Input(Script'Access);
   end Read_Face;

   function Read_MP_Type return Magic_Type
   is
   begin
      return Magic_Type'Input(Script'Access);
   end Read_MP_Type;

   function Read_Story_Movie return Story_Movie_Type 
   is
   begin
      return Story_Movie_Type'Input(Script'Access);
   end Read_Story_Movie;

   function Read_Item_Grid return Item_Grid_Type
   is
   begin
      return Item_Grid_Type'Input(Script'Access);
   end Read_Item_Grid;

   function Get_Story (Id : Scene_Story_Id) return Scene_Story_Id
   is
   begin
      return (
         Scene => (if Id.Scene = Unchanged then 
            Stages.Current_Stage.Get_Scene_Id
         else
            Id.Scene),
         Story => (if Id.Story = Unchanged then
            Stages.Current_Stage.Get_Story_Id
         else
            Id.Story));
   end Get_Story;


   function Get_Story (Id : Scene_Story_Id) return access Story_Type
   is
   begin
      if Id.Story = Unchanged then
         return Stages.Current_Stage.Current_Story;
      else
         if Id.Scene /= Unchanged then
            return All_Scene_Stories(Id.Scene)(Id.Story)'Access;
         else
            return Stages.Current_Stage.Stories.all(Id.Story)'Access;
         end if;
      end if;
   end Get_Story;

   procedure Emit (Message : UTF8_String)
   is
   begin
      Stages.Current_Stage.Myself.Emit(Message, Colors.Yellow);
   end Emit;

   procedure Load_Script (Id : Uint16_t)
   is
      reader : Legend.Grps.Group_File_Type;
   begin
      reader.Open (JY_Path & "kdef.idx", JY_Path & "kdef.grp");
      reader.Set_Block_Index(Id);
      Script.Reset_For_Write;
      Script.Copy_From(
         reader.Data_Stream.all, 
         Stream_Element_Offset(reader.Block_Size));
      reader.Close;
      Script.Reset_For_Read;
   end Load_Script;

   -- Note 
   -- Ada function push stack order is from right to left
   -- so, some_func(read_uint16, read_uint16) won't work as desired
   -- to correct this problem, 
   -- declare stdcall to some_func or read argument from stream one by one
   procedure Execute_Script (Id : Uint16_t)
   is
      cmd : Uint16_t;
      r : Boolean;
      p1, p2 : Uint16_t;
      procedure Read_Jump_Size
      is
      begin
         p1 := Read_Uint16;
         p2 := Read_Uint16;
      end Read_Jump_Size;

      procedure Jump
      is
         dummy : Uint16_t;
      begin
         for i in 1 .. (if r then p1 else p2) loop
            dummy := Read_Uint16;
         end loop;
      end Jump;

      procedure Goto_Branch(Result : Boolean)
      is
      begin
         r := Result;
         Read_Jump_Size;
         Jump;
      end Goto_Branch;

   begin
      loop
         cmd := Read_Uint16;
         case cmd is
            when 0 =>
               Clear_Screen;
            when 1 =>
               declare
                  Talk : Uint16_t := Read_Uint16;
                  Icon : Uint16_t := Read_Uint16;
                  Side : Uint16_t := Read_Uint16;
               begin
                  Show_Talk(Icon => Icon, Talk => Talk, Side => Side);
               end;
            when 2 =>
               Gain_Item(Read_Item_Grid);
            when 3 =>
               declare
                  Id       : Scene_Story_Id := Read_Scene_Story_Id;
                  Story    : Story_Type := Read_Story;
               begin
                  Change_Story(Id, Story);
               end;
            when 4 =>
               Goto_Branch(Is_Story_Item_Selected(Read_Uint16));
            when 5 =>
               Goto_Branch(Confirm_Fight);
            when 6 =>
               r := Fight(Read_Uint16);
               Read_Jump_Size;
               -- is_safe
               if not Read_Boolean then
                  if not r then
                     Gameover := True;
                     Trace_Log("you die");
                  end if;
               end if;
               Jump;
            when 7 =>
               Trace_Log("break script");
               return;
            when 8 =>
               Set_Main_Map_Music(Read_Uint16);
            when 9 =>
               Goto_Branch(Confirm_Hire);
            when 10 =>
               Hire(Read_Uint16);
            when 11 =>
               Goto_Branch(Confirm_Sleep);
            when 12 =>
               Sleep;
            when 13 =>
               Light_On;
            when 14 =>
               Light_Off;
            when 15 =>
               Die(Read_Uint16);
               exit;
            when 16 =>
               Goto_Branch(Is_Hired(Read_Uint16));
            when 17 =>
               declare
                  Scene_Id : Uint16_t := Read_Uint16;
                  Layer_Id : Map_Layer_Type := Read_Map_Layer;
                  Where    : Point_Type := Read_Point;
                  Value    : Uint16_t := Read_Uint16;
               begin
                  Modify_Map(Scene_Id, Layer_Id, Where, Value);
               end;
            when 18 =>
               Goto_Branch(Is_Misc_Item_In_Bag(Read_Uint16));
            when 19 =>
               Set_My_Position(Read_Point);
            when 20 =>
               Goto_Branch(Is_Team_Full);
            when 21 =>
               Fire(Read_Uint16);
            when 22 =>
               Set_Team_MP_To_Zero;
            when 23 =>
               declare
                  Hero : Uint16_t := Read_Uint16;
                  Value : Int16_t := Read_Int16;
               begin
                  Set_Hero_Poison_Skill(Hero, Value);
               end;
            when 24 =>
               null;
            when 25 =>
               declare
                  From : Point_Type := Read_Point;
                  To : Point_Type := Read_Point;
               begin
                  Scroll_Map_As_Movie(From, To);
               end;
            when 26 =>
               declare
                  Id       : Scene_Story_Id := Read_Scene_Story_Id;
                  Offsets  : Script_Of_Cause(Script_Cause_Type);
               begin
                  for offset of Offsets loop
                     offset := Read_Uint16;
                  end loop;
                  Incr_Script_Index(Id, Offsets);
               end;
            when 27 =>
               Play_Story_Movie(Read_Story_Movie);
            when 28 =>
               declare
                  Hero : Uint16_t := Read_Uint16;
                  From : Int16_t := Read_Int16;
                  To   : Int16_t := Read_Int16;
               begin
                  Goto_Branch(Is_Morality_In_Range(Hero, From, To));
               end;
            when 29 =>
               declare
                  Hero : Uint16_t := Read_Uint16;
                  From : Int16_t := Read_Int16;
                  To   : Int16_t := Read_Int16;
               begin
                  Goto_Branch(Is_Attack_In_Range(Hero, From, To));
               end;
            when 30 =>
               declare
                  From : Point_Type := Read_Point;
                  To   : Point_Type := Read_Point;
               begin
                  Walk_To(From, To);
               end;
            when 31 =>
               Goto_Branch(Is_Money_Enougth(Read_Int16));
            when 32 =>
               Add_Item_Count(Read_Item_Grid);
            when 33 =>
               declare
                  Hero : Uint16_t := Read_Uint16;
                  Kongfu : Uint16_t := Read_Uint16;
                  Silent : Boolean := Read_Boolean;
               begin
                  Learn_Kongfu(Hero, Kongfu, Silent);
               end;
            when 34 =>
               declare
                  Hero : Uint16_t := Read_Uint16;
                  Count : Int16_t := Read_Int16;
               begin
                  Incr_Wise_With_Hint(Hero, Count);
               end;
            when 35 =>
               declare
                  Hero : Uint16_t := Read_Uint16;
                  Grid : Uint16_t := Read_Uint16;
                  Kongfu : Uint16_t := Read_Uint16;
                  Level : Int16_t := Read_Int16;
               begin
                  Modify_Learned_Kongfu(Hero, Grid, Kongfu, Level);
               end;
            when 36 =>
               Goto_Branch(Is_My_Sex_Equals_To(Read_Sex));
            when 37 =>
               Incr_My_Morality(Read_Int16);
            when 38 =>
               declare
                  Scene_Id : Uint16_t := Read_Uint16;
                  Layer_Id : Map_Layer_Type := Read_Map_Layer;
                  Old_Pic  : Uint16_t := Read_Uint16;
                  New_Pic  : Uint16_t := Read_Uint16;
               begin
                  Replace_Map_Picture(Scene_Id, Layer_Id, Old_Pic, New_Pic);
               end;
            when 39 =>
               Open_Scene(Read_Uint16);
            when 40 =>
               Set_My_Face(Read_Face);
            when 41 =>
               declare
                  npc : Uint16_t := Read_Uint16;
                  item : Uint16_t := Read_Uint16;
                  count : Int16_t := Read_Int16;
               begin
                  Put_Item_To_NPC_Bag(npc, item, count);
               end;
            when 42 =>
               Goto_Branch(Is_Woman_In_Team);
            when 43 =>
               Goto_Branch(Is_Item_In_Bag(Read_Uint16));
            when 44 =>
               declare
                  m1 : Story_Movie_Type := Read_Story_Movie;
                  m2 : Story_Movie_Type := Read_Story_Movie;
               begin
                  Play_Story_Movies(m1, m2);
               end;
            when 45 =>
               declare
                  hero : Uint16_t := Read_Uint16;
                  count : Int16_t := Read_Int16;
               begin
                  Incr_Movement(hero, count);
               end;
            when 46 =>
               declare
                  hero : Uint16_t := Read_Uint16;
                  count : Int16_t := Read_Int16;
               begin
                  Incr_MP_Limit(hero, count);
               end;
            when 47 =>
               declare
                  hero : Uint16_t := Read_Uint16;
                  count : Int16_t := Read_Int16;
               begin
                  Incr_Attack(hero, count);
               end;
            when 48 =>
               declare
                  hero : Uint16_t := Read_Uint16;
                  count : Int16_t := Read_Int16;
               begin
                  Incr_HP_Limit(hero, count);
               end;
            when 49 =>
               declare
                  hero : Uint16_t := Read_Uint16;
                  mp_t : Magic_Type := Read_MP_Type;
               begin
                  Modify_MP_Type(hero, mp_t);
               end;
            when 50 =>
               Goto_Branch(Is_Items_In_Bag(Read_Uint16_Array(5)));
            when 51 =>
               Show_Random_Talk_By_Softboy;
            when 52 =>
               Show_Morality;
            when 53 =>
               Show_Fame;
            when 54 =>
               Open_All_Scenes;
            when 55 =>
               declare
                  Story_Id : Uint16_t := Read_Uint16;
                  Script_Id : Uint16_t := Read_Uint16;
               begin
                  Goto_Branch(Is_Script_Index_Of_Space_Equals_To(Story_Id, Script_Id));
               end;
            when 56 =>
               Incr_My_Fame(Read_Int16);
            when 57 =>
               Enter_Gaochangmigong;
            when 58 =>
               Huashanlunjian;
            when 59 =>
               Fire_Team;
            when 60 =>
               declare
                  Id       : Scene_Story_Id := Read_Scene_Story_Id;
                  Pic_Id   : Uint16_t := Read_Uint16;
               begin
                  Goto_Branch(Is_Story_Picture_Equals_To(Id, Pic_Id));
               end;
            when 61 =>
               Goto_Branch(Is_All_Books_Placed);
            when 62 =>
               declare
                  M1 : Story_Movie_Type := Read_Story_Movie;
                  M2 : Story_Movie_Type := Read_Story_Movie;
               begin
                  Play_Time_Machine_Movie(M1, M2);
               end;
            when 63 =>
               declare
                  hero : Uint16_t := Read_Uint16;
                  sex : Sex_Type := Read_Sex;
               begin
                  Modify_Sex(hero, sex);
               end;
            when 64 =>
               Shopping;
            when 65 =>
               Open_Random_Shop_System;
            when 66 =>
               Scroll_Map_To(Read_Point);
            when 67 =>
               Play_Sound(Read_Uint16);
            when No_Script =>
               exit;
            when others =>
               null;
         end case;
      end loop;
   end Execute_Script;

   procedure Invoke_Story (Script : Uint16_t)
   is
   begin
      Trace_Log("invoke_story(script =>" & Script'Img & ")");
      Load_Script(Script);
      Execute_Script(Script);
   end Invoke_Story;

   procedure Clear_Screen
   is
      stage : Stages.Stage_Pointer := Stages.Current_Stage;
   begin
      Trace_Log("clear_screen");
      stage.Canvas.Invalidate;
      stage.Canvas.Repaint;
   end Clear_Screen;

   procedure Show_Talk (Talk : Uint16_t; Icon : Uint16_t; Side : Uint16_t)
   is
      S : String := (if Talk = No_Talk then "..." else Resources.All_Talks(Talk).all); 
   begin
      Trace_Log("show_talk(talk =>" & Talk'Img & ", Icon =>" & Icon'Img & ", Side =>" & Side'Img & ")");
      declare
         d : Legend.Dialogs.Dialog_Type;
      begin
         d.Prompt(S);
         d.Linefeed("*");
         d.Add_Expect(SDL.Keysym.K_Space);
         d.Add_Expect(SDL.Keysym.k_Return);
         d.Add_Expect(SDL.Keysym.K_Y);
         d.Add_Expect(SDL.Keysym.K_N);
         if Icon in Resources.Head_Images'Range then
            d.Icon(Resources.Head_Images(Icon));
         end if;
         d.Run;
      end;
   end Show_Talk;

   procedure Gain_Item (Which : Item_Grid_Type)
   is
      Item : Item_Type renames All_World.Items(Which.Id);
      bag : Bag_Type renames All_World.Leader.Bag; 
      grid : Natural := Empty_Grid;
   begin
      Trace_Log("gain_item(" & Traces.Image(Which) & ")");
      Stages.Current_Stage.Say(StrRes.Gain_Item & To_UTF8_Name(Item.Name));
      for i in bag'Range loop
         if bag(i).Id = Which.Id then
            bag(i).Count := bag(i).Count + Which.Count;
            return;
         end if;
         if grid = Empty_Grid then
            if bag(i).Id = No_Item then
               grid := i;
            end if;
         end if;
      end loop;
      if grid /= Empty_Grid then
         bag(grid) := Which;
      end if;
   end Gain_Item;

   procedure Change_Story (Id : Scene_Story_Id; Story : Story_Type)
   is
      use Traces;
      full_id : Scene_Story_Id := Get_Story(Id);
      s : Story_Type renames All_Scene_Stories(full_id.Scene)(full_id.Story);
      subtype As_Array is Uint16_Array(1 .. Story_Type'Size / 16);
      pragma Warnings(Off);
      src : As_Array with Import, Convention => Ada, Address => Story'Address;
      dst : As_Array with Import, Convention => Ada, Address => s'Address;
      pos_changed : Boolean := Story.Position /= (-2, -2);
      map : Layer_Access := All_Scene_Maps(full_id.Scene)(ml_Story);
   begin
      Trace_Log(
         "change_story(scene_id =>" & Image_Uint16(Id.Scene) & 
         ", story_id =>" & Image_Uint16(Id.Story) &
         ", story =>" & Image(Story) & ")");
      if pos_changed then
         map(s.Position.y, s.Position.x) := No_Story;
      end if;
      for i in src'Range loop
         if src(i) /= Unchanged then
            dst(i) := src(i);
         end if;
      end loop;
      if pos_changed then
         map(Story.Position.y, Story.Position.x) := full_id.Story;
      end if;
   end Change_Story;

   function Is_Story_Item_Selected (Story_Item : Uint16_t) return Boolean
   is
   begin
      Trace_Log("is_story_item_selected(item =>" & Story_Item'Img & ")");
      return Selected_Story_Item = Story_Item;
   end Is_Story_Item_Selected;

   function Confirm_Fight return Boolean
   is
      Stage : Stages.Stage_Pointer := Stages.Current_Stage;
   begin
      Trace_Log("confirm_fight");
      return Stage.Confirm(StrRes.Confirm_Fight);
   end Confirm_Fight;

   function Fight (War : Uint16_t) return Boolean
   is
      use type Menus.Menuitem_Pointer;
      s : Stages.Stage_Pointer := Stages.Current_Stage;
      W : War_Define_Type := Resources.All_War_Defines(War);
   begin
      Trace_Log("fight(war =>" & Traces.Image_Uint16(War) & ")");
      if (for some h of W.Required_Heroes => h /= No_Hero) then
         s.Select_War_Team(W);
      end if;
      return s.Switch_To_War(W) = k_Player;
   end Fight;

   procedure Set_Main_Map_Music (Music : Uint16_t)
   is
   begin
      Trace_Log("set_main_map_Music(music =>" & Music'Img & ")");
      Legend.Music.Play_Background_Music(Music);
   end Set_Main_Map_Music;

   function Confirm_Hire return Boolean
   is
   begin
      Trace_Log("confirm_hire");
      return Stages.Current_Stage.Confirm(StrRes.Confirm_Hire);
   end Confirm_Hire;

   procedure Hire (Hero : Uint16_t)
   is
   begin
      Trace_Log("hire");
      for who of All_World.Leader.Team loop
         if who = No_Hero then
            who := Hero;
            declare
               H : Hero_Type renames All_World.Heroes(Hero);
            begin
               for I in H.Items'Range loop
                  if H.Items(I) /= No_Item and then H.Items_Count(I) > 0 then
                     Gain_Item((H.Items(I), H.Items_Count(I)));
                     H.Items(I) := No_Item;
                     H.Items_Count(I) := 0;
                  end if;
               end loop;
            end;
            return;
         end if;
      end loop;
      Emit(StrRes.Your_Team_Is_Full);
   end Hire;

   function Confirm_Sleep return Boolean
   is
   begin
      Trace_Log("confirm_sleep");
      return Stages.Current_Stage.Confirm(StrRes.Confirm_Sleep);
   end Confirm_Sleep;

   procedure Sleep is
   begin
      Trace_Log("sleep");
      for h of All_World.Leader.Team loop
         if h /= No_Hero then
            Reset_State(All_World.Heroes(h));
         end if;
      end loop;
   end Sleep;

   procedure Light_On is
      stage : Stages.Stage_Pointer := Stages.Current_Stage;
      fog : Controls.Fog_Access := Stages.Current_Stage.Get_Fog;
      life : Time := Clock + 0.5;
      function Over return Boolean
      is
      begin
         return Clock > life;
      end Over;
   begin
      Trace_Log("light_on");
      fog.Back_Color.A := 128;
      fog.Paint;
      fog.Life := Clock + 1.0;
      fog.Visible := True;
      stage.Add_Control(Controls.Control_Access(fog));
      stage.Switch_To_Movie_Mode(Over'Access);
      fog.Visible := False;
      stage.Canvas.Invalidate;
      stage.Canvas.Repaint;
   end Light_On;

   procedure Light_Off is
      stage : Stages.Stage_Pointer := Stages.Current_Stage;
      fog : Controls.Fog_Access := stage.Get_Fog;
      life : Time := Clock + 0.5;
      function Over return Boolean
      is
      begin
         return Clock > life;
      end Over;
   begin
      Trace_Log("light_off");
      fog.Back_Color.A := 255;
      fog.Paint;
      fog.Life := Clock + 1.0;
      fog.Visible := True;
      stage.Add_Control(Controls.Control_Access(fog));
      stage.Switch_To_Movie_Mode(Over'Access);
   end Light_Off;

   procedure Die(Mode : Uint16_t) is
   begin
      Trace_Log("die(mode =>" & Mode'Img & ")");
      Stages.Close_All_Stage;
   end Die;

   function Is_Hired(Hero : Uint16_t) return Boolean
   is
      fs : Uint16_Array renames All_World.Leader.Team;
   begin
      Trace_Log("is_hired(hero =>" & Hero'Img & ")");
      return (for some h of fs => h = Hero);
   end Is_Hired;

   procedure Modify_Map (
      Scene : Uint16_t;
      Layer : Map_Layer_Type;
      Where : Point_Type;
      Value : Uint16_t
      )
   is
   begin
      Trace_Log(
         "modify_map(scene =>" & Traces.Image_Uint16(Scene) & 
         ", layer =>" & Layer'Img &
         ", where =>" & Traces.Image(Where) &
         ", value =>" & Value'Img & ")");
      if Scene = Unchanged then
         Stages.Current_Stage.Set(Layer, Where, Value);
      else
         if Scene in All_Scene_Maps'Range then
            declare
               map : Layer_Access := All_Scene_Maps(Scene)(Layer);
            begin
               if In_Bound(map, Where) then
                  map(Where.y, Where.x) := Value;
               end if;
            end;
         end if;
      end if;
   end Modify_Map;

   function Is_Misc_Item_In_Bag (Item_Id : Uint16_t) return Boolean
   is
   begin
      Trace_Log("is_misc_item_in_bag(item =>" & Item_Id'Img & ")");
      return (for some item of All_World.Leader.Bag => item.Id = Item_Id);
   end Is_Misc_Item_In_Bag;

   procedure Set_My_Position (Where : Point_Type)
   is
      s : Stages.Stage_Pointer := Stages.Current_Stage;
      p : Point_Type := s.Myself.Position;
   begin
      if Where.x /= -2 then
         p.x := Where.x;
      end if;
      if Where.y /= -2 then
         p.y := Where.y;
      end if;
      Trace_Log("set_my_position(p =>" & Traces.Image(p) & ")");
      s.Myself.Position(p);
      s.Myself.Focus;
   end Set_My_Position;

   function Is_Team_Full return Boolean
   is
   begin
      Trace_Log("is_team_full");
      return (for all h of All_World.Leader.Team => h /= No_Hero);
   end Is_Team_Full;

   procedure Fire (Hero : Uint16_t) 
   is
      fs : Uint16_Array renames All_World.Leader.Team;
   begin
      Trace_Log("fire(hero =>" & Hero'Img & ")");
      if Hero = No_Hero then
         return;
      end if;
      for who of All_World.Leader.Team loop
         if who = Hero then
            who := No_Hero;
            declare
               H : Hero_Type renames All_World.Heroes(Hero);
            begin
               for Equip in Equip_Type loop
                  Unequip_Item(H, Equip);
               end loop;
               Unequip_Book(H);
            end;
            exit;
         end if;
      end loop;
   end Fire;

   procedure Set_Team_MP_To_Zero
   is
   begin
      Trace_Log("set_team_mp_to_zero");
      for who of All_World.Leader.Team loop
         if who /= No_Hero then
            All_World.Heroes(who).MP.Now := 0;
         end if;
      end loop;
   end Set_Team_MP_To_Zero;

   procedure Set_Hero_Poison_Skill (Hero : Uint16_t; Value : Int16_t)
   is
   begin
      Trace_Log("set_hero_poison_skill(hero =>" & Hero'Img & ", value =>" & Value'Img & ")");
      for who of All_World.Leader.Team loop
         if who = Hero then
            All_World.Heroes(who).Skills(s_Poison) := Value;
            exit;
         end if;
      end loop;
   end Set_Hero_Poison_Skill;

   procedure Scroll_Map_As_Movie (From, To : Point_Type)
   is
      use Traces;
      stage : Stages.Stage_Pointer := Stages.Current_Stage;
   begin
      Trace_Log("scroll_map_as_movie(from => " & Image(From) & ", to => " & Image(To) & ")");
      stage.Scroll_To_Canvas_Center(From);
      stage.Canvas.Repaint;
      delay 1.0;
      stage.Scroll_To_Canvas_Center(To);
      stage.Canvas.Repaint;
   end Scroll_Map_As_Movie;

   procedure Incr_Script_Index(
      Id            : Scene_Story_Id;
      Delta_Indexes : Script_Of_Cause
      )
   is
      S : access Story_Type := Get_Story(Id);

      procedure Incr (Script_Index : in out Uint16_t; Value : Uint16_t)
      is
      begin
         if Value /= Unchanged then
            Script_Index := Script_Index + Value;
         end if;
      end Incr;
   begin
      Trace_Log(
         "incr_script_index(scene =>" & Traces.Image_Uint16(Id.Scene) & 
         ", story =>" & Traces.Image_Uint16(Id.Story) & 
         ", delta =>" & Traces.Image(Delta_Indexes, True) & ")");
      for i in Delta_Indexes'Range loop
         Incr(S.Scripts(i), Delta_Indexes(i));
      end loop;
   end Incr_Script_Index;

   procedure Play_Story_Movie (Movie : Story_Movie_Type)
   is
   begin
      Trace_Log("play_story_movie(movie =>" & Traces.Image(Movie) & ")");
      Stages.Current_Stage.Play_Story_Movie(Movie);
   end Play_Story_Movie;

   function Is_Morality_In_Range (
      Hero : Uint16_t;
      From : Int16_t;
      To   : Int16_t
      ) return Boolean
   is
   begin
      Trace_Log("is_morality_in_range(hero =>" & Hero'Img & ", from =>" & From'Img & ", to =>" & To'Img & ")");
      return Debug_Mode or else All_World.Heroes(Hero).Morality in From .. To;
   end Is_Morality_In_Range;

   function Is_Attack_In_Range (
      Hero : Uint16_t;
      From : Int16_t;
      To   : Int16_t
      ) return Boolean
   is
   begin
      Trace_Log("is_attack_in_range(hero =>" & Hero'Img & ", from =>" & From'Img & ", to =>" & To'Img & ")");
      return Debug_Mode or else All_World.Heroes(Hero).Skills(s_Attack) in From .. To;
   end Is_Attack_In_Range;

   procedure Walk_To (From, To : Point_Type)
   is
      use Traces;
   begin
      Trace_Log("walk_to(from => " & Image(From) & ", to => " & Image(To) & ")");
      Stages.Current_Stage.Myself.Move_To(To);
   end Walk_To;

   function Is_Money_Enougth (Expected : Int16_t) return Boolean
   is
   begin
      Trace_Log("is_money_enougth(expected =>" & Expected'Img & ")");
      for b of All_World.Leader.Bag loop
         if b.Id = Resources.Money_Id then
            return b.Count >= Expected;
         end if;
      end loop;
      return False;
   end Is_Money_Enougth;

   procedure Add_Item_Count (Which : Item_Grid_Type)
   is
   begin
      Trace_Log("add_item_count(" & Traces.Image(Which) & ")");
      for b of All_World.Leader.Bag loop
         if b.Id = Which.Id then
            Add(b.Count, Which.Count, (0, Int16_t'Last));
            exit;
         end if;
      end loop;
   end Add_Item_Count;

   procedure Learn_Kongfu (
      Hero   : Uint16_t;
      Kongfu : Uint16_t;
      Silent : Boolean
      )
   is
      who : Hero_Type renames All_World.Heroes(Hero);
      grid : Positive := Empty_Grid; 
   begin
      Trace_Log(
         "learn_kongfu(hero =>" & Hero'Img & 
         ", kongfu =>" & Kongfu'Img & 
         ", silent => " & Silent'Img & ")");
      for i in who.Kongfu'Range loop
         if who.Kongfu(i) in 0 | No_Kongfu then
            grid := i;
            exit;
         end if;
      end loop;
      if grid = Empty_Grid then
         grid := 1;
      end if;
      who.Kongfu(grid) := Kongfu;
      who.Kongfu_Exp(grid) := 0;
      if not Silent then
         declare
            s1 : UTF8_String := To_UTF8_Name(who.Name);
            s2 : UTF8_String := To_UTF8_Name(All_World.Kongfus(Kongfu).Name);
         begin
            Stages.Current_Stage.Say(s1 & StrRes.Learnd & s2, Icon => No_Hero);
         end;
      end if;
   end Learn_Kongfu;

   procedure Incr_Wise_With_Hint (Hero : Uint16_t; Count : Int16_t)
   is
      who : Hero_Type renames All_World.Heroes(Hero);
   begin
      Trace_Log("incr_wise_with_hint(hero =>" & Hero'Img & ", count =>" & Count'Img & ")");
      Add(who.Wise, Count, Degree_Range);
      Emit(To_UTF8_Name(who.Name) & StrRes.Inc_Wise & Count'Img);
   end Incr_Wise_With_Hint;

   procedure Modify_Learned_Kongfu (
      Hero   : Uint16_t;
      Index  : Uint16_t;
      Kongfu : Uint16_t;
      Exp    : Int16_t
      )
   is
      who : Hero_Type renames All_World.Heroes(Hero);
      g : Kongfu_Index_Range := Kongfu_Index_Range(Index);
   begin
      Trace_Log(
         "modify_learned_kongfu(hero =>" & Hero'Img & 
         ", grid =>" & Index'Img & 
         ", kongfu =>" & Kongfu'Img & 
         ", exp =>" & Exp'Img & ")");
      who.Kongfu(g) := Kongfu;
      who.Kongfu_Exp(g) := Exp;
   end Modify_Learned_Kongfu;

   function Is_My_Sex_Equals_To (Value : Sex_Type) return Boolean
   is
   begin
      Trace_Log("is_my_sex_equals_to(sex => " & Value'Img & ")");
      return All_World.Heroes(0).Sex = Value;
   end Is_My_Sex_Equals_To;

   procedure Incr_My_Morality (Count : Int16_t)
   is
   begin
      Trace_Log("incr_my_morality(count =>" & Count'Img & ")");
      Add(All_World.Heroes(0).Morality, Count, Points_Range);
   end Incr_My_Morality;

   procedure Replace_Map_Picture (
      Scene_Id : Uint16_t;
      Layer_Id : Map_Layer_Type;
      Old_Pic  : Uint16_t;
      New_Pic  : Uint16_t
      )
   is
      use Traces;
   begin
      Trace_Log(
         "replace_map_picture(scene =>" & Image_Uint16(Scene_Id) & 
         ", layer =>" & Layer_Id'Img &
         ", old_pic =>" & Image_Uint16(Old_Pic) & 
         ", new_pic =>" & Image_Uint16(New_Pic));
      if Scene_Id = Unchanged then
         Stages.Current_Stage.Replace_Map(Layer_Id, Old_Pic, New_Pic);
      else
         for v of All_Scene_Maps(Scene_Id)(Layer_Id).all loop
            if v = Old_Pic then
               v := New_Pic;
            end if;
         end loop;
      end if;
   end Replace_Map_Picture;

   procedure Open_Scene (Scene_Id : Uint16_t)
   is
   begin
      Trace_Log("open_scene(scene =>" & Traces.Image_Uint16(Scene_Id) & ")");
      All_World.Scenes(Scene_Id).Enter_Condition := 0;
   end Open_Scene;

   procedure Set_My_Face (Face : Face_Type)
   is
   begin
      Trace_Log("set_my_face(face => " & Face'Img & ")");
      Stages.Current_Stage.Myself.Face(Face);
   end Set_My_Face;

   procedure Put_Item_To_NPC_Bag (
      NPC  : Uint16_t; 
      Item : Uint16_t; 
      Count : Int16_t
      )
   is
      who : Hero_Type renames All_World.Heroes(NPC);
   begin
      Trace_Log("put_item_to_npc_bag(npc =>" & NPC'Img & ", item =>" & Item'Img & ", count =>" & Count'Img & ")");
      for i in who.Items'Range loop
         if who.Items(i) = Empty_Grid or who.Items_Count(i) = 0 then
            who.Items(i) := Item;
            who.Items_Count(i) := Count;
         end if;
      end loop;
   end Put_Item_To_NPC_Bag;

   function Is_Woman_In_Team return Boolean
   is
   begin
      Trace_Log("is_woman_in_team");
      for h of All_World.Leader.Team loop
         if h /= No_Hero then
            if All_World.Heroes(h).Sex = Woman then
               return True;
            end if;
         end if;
      end loop;
      return False;
   end Is_Woman_In_Team;

   function Is_Item_In_Bag (Item_Id : Uint16_t) return Boolean
   is
   begin
      Trace_Log("is_item_in_bag(item =>" & Item_Id'Img & ")");
      return (for some g of All_World.Leader.Bag => g.Id = Item_Id and then g.Count > 0);
   end Is_Item_In_Bag;

   procedure Play_Story_Movies (M1, M2 : Story_Movie_Type)
   is
   begin
      Trace_Log("play_story_movies(m1 =>" & Traces.Image(M1) & ", M2 => " & Traces.Image(M2) & ")");
      Stages.Current_Stage.Play_Story_Movies((M1, M2));
   end Play_Story_Movies;

   procedure Incr_Movement (Hero : Uint16_t; Count : Int16_t)
   is
      who : Hero_Type renames All_World.Heroes(Hero);
   begin
      Trace_Log("incr_movement(hero =>" & Hero'Img & ", count =>" & Count'Img & ")");
      Add(who.Skills(s_Movement), Count, Degree_Range);
   end Incr_Movement;

   procedure Incr_MP_Limit (Hero : Uint16_t; Count : Int16_t)
   is
      who : Hero_Type renames All_World.Heroes(Hero);
   begin
      Trace_Log("incr_mp_limit(hero =>" & Hero'Img & ", count =>" & Count'Img & ")");
      Add(who.MP.Max, Count, Points_Range);
   end Incr_MP_Limit;

   procedure Incr_Attack (Hero : Uint16_t; Count : Int16_t)
   is
      who : Hero_Type renames All_World.Heroes(Hero);
   begin
      Trace_Log("incr_attack(hero =>" & Hero'Img & ", count =>" & Count'Img & ")");
      Add(who.Skills(s_Attack), Count, Degree_Range);
   end Incr_Attack;

   procedure Incr_HP_Limit (Hero : Uint16_t; Count : Int16_t)
   is
      who : Hero_Type renames All_World.Heroes(Hero);
   begin
      Trace_Log("incr_hp_limit(hero =>" & Hero'Img & ", count =>" & Count'Img & ")");
      Add(who.HP.Max, Count, Points_Range);
   end Incr_HP_Limit;

   procedure Modify_MP_Type (Hero : Uint16_t; Value : Magic_Type)
   is
      who : Hero_Type renames All_World.Heroes(Hero);
   begin
      Trace_Log("modify_mp_type(hero =>" & Hero'Img & ", type =>" & Value'Img & ")");
      who.MP_Type := Value;
   end Modify_MP_Type;

   function Is_Items_In_Bag (Items : Uint16_Array) return Boolean
   is
      type Item_Flags is array(Items'Range) of Boolean;
      Empty : constant Item_Flags := (others => False);
      Full  : constant Item_Flags := (others => True);
      flags : Item_Flags := Empty;
   begin
      Trace_Log("is_items_in_bag(" & Traces.Image(Items) & ")");
      for grid of All_World.Leader.Bag loop
         for i in Items'Range loop
            if grid.Id = Items(i) then
               flags(i) := grid.Count > 0;
               exit;
            end if;
         end loop;
      end loop;
      return flags = Full;
   end Is_Items_In_Bag;

   procedure Show_Random_Talk_By_Softboy
   is
      use Resources;
      N : Uint16_t := Uint16_t(Random_Natural(Rnd_Generator, 2547, 2564));
   begin
      Trace_Log("show_random_talk_by_softboy");
      Stages.Current_Stage.Say(All_Talks(N).all, Icon => Softboy_Icon);
   end Show_Random_Talk_By_Softboy;

   procedure Show_Morality
   is
   begin
      Trace_Log("show_morality");
      Emit(StrRes.Your_Morality & All_World.Heroes(0).Morality'Img);
   end Show_Morality;

   procedure Show_Fame
   is
   begin
      Trace_Log("show_fame");
      Emit(StrRes.Your_Fame & All_World.Heroes(0).Morality'Img);
   end Show_Fame;

   procedure Open_All_Scenes
   is
      Scenes : Scenes_Type renames All_World.Scenes.all;
      -- after new game, only 5 inns, nanyuanju, leader home are opened,
      -- after talk with nanyuan, 
      -- all scenes are opened except taohuadao & jueqingjudi
      -- yunheya & motianya require movement >= 80
   begin
      Trace_Log("open_all_scenes");
      for s of Scenes loop
         s.Enter_Condition := 0;
      end loop;
      Scenes(75).Enter_Condition := 1;
      Scenes(80).Enter_Condition := 1;
      Scenes(2).Enter_Condition := 2;
      Scenes(38).Enter_Condition := 2;
   end Open_All_Scenes;

   function Is_Script_Index_Of_Space_Equals_To (
      Story_Id : Uint16_t;
      Script_Index : Uint16_t
      ) return Boolean
   is
      S : Story_Type renames Stages.Current_Stage.Stories(Story_Id);
   begin
      Trace_Log(
         "is_script_index_of_space_equals_to(story =>" & Traces.Image_Uint16(Story_Id) &
         ", script =>" & Traces.Image_Uint16(Script_Index));
      return S.Scripts(sc_Space) = Script_Index;
   end Is_Script_Index_Of_Space_Equals_To;

   procedure Incr_My_Fame (Count : Int16_t) 
   is
   begin
      Trace_Log("incr_my_fame(delta =>" & Count'Img & ")");
      Add(All_World.Heroes(0).Fame, Count, Points_Range);
   end Incr_My_Fame;

   procedure Enter_Gaochangmigong
   is
      story_movie : Story_Movie_Type := (No_Story, 3831 * 2, 3844 * 2);
   begin
      Trace_Log("enter_gaochangmigong");
      Stages.Current_Stage.Play_Story_Movie(story_movie);
   end Enter_Gaochangmigong;

   procedure Huashanlunjian
   is
      use Resources;
      Stage : Stages.Stage_Pointer := Stages.Current_Stage;
      subtype Dialogue is Uint16_t range 2854 .. 2883;
      subtype War      is Uint16_t range 102  .. 131;
      Leader : Hero_Type renames All_World.Heroes(0);
      Xuanci : Hero_Type renames All_World.Heroes(70);
      Yuanchengzhi : Hero_Type renames All_World.Heroes(54);
   begin
      Trace_Log("huashanlunjian");
      for i in Uint16_t range 0 .. 29 loop 
         declare
            W : War_Define_Type := All_War_Defines(War'First + i);
            Op : Hero_Type renames All_World.Heroes(W.Enemies.Id(1));
         begin
            Trace_Log("fight(war =>" & Traces.Image_Uint16(W.Id) & ")");
            Stage.Say(All_Talks(Dialogue'First + i).all, Icon => Op.Icon);
            if Stage.Switch_To_War(W) /= k_Player then
               return;
            end if;
         end;
         Stage.Canvas.Repaint;
         Stage.Say(All_Talks(2890).all, Icon => Leader.Icon);
         if i mod 3 = 0 then
            Stage.Say(All_Talks(2891).all, Icon => Xuanci.Icon);
            Leader.HP.Now := Leader.HP.Max;
            Leader.MP.Now := Leader.MP.Max;
            Leader.Stamina := Degree_Range.Max;
            Stage.Say(All_Talks(2892).all, Icon => Leader.Icon);
         end if;
      end loop;
      Stage.Say(All_Talks(2884).all, Icon => Leader.Icon);
      Stage.Say(All_Talks(2885).all, Icon => Xuanci.Icon);
      Gain_Item((143, 1));
      Stage.Say(All_Talks(2886).all, Icon => Xuanci.Icon);
      Stage.Say(All_Talks(2887).all, Icon => Yuanchengzhi.Icon);
      Stage.Say(All_Talks(2888).all, Icon => Xuanci.Icon);
      Stage.Say(All_Talks(2889).all, Icon => Leader.Icon);
   end Huashanlunjian;

   procedure Fire_Team
   is
   begin
      Trace_Log("fire_team");
      All_World.Leader.Team := (others => No_Hero);
   end Fire_Team;

   function Is_Story_Picture_Equals_To (
      Id       : Scene_Story_Id;
      Pic_Id   : Uint16_t
      ) return Boolean
   is
      S : access Story_Type := Get_Story(Id);
   begin
      Trace_Log(
         "is_story_picture_equals_to(scene =>" & Traces.Image_Uint16(Id.Scene) & 
         ", story =>" & Traces.Image_Uint16(Id.Story) & 
         ", picture =>" & Traces.Image_Uint16(Pic_Id) & ")");
      return S.Movie_Frame.Current = Pic_Id;
   end Is_Story_Picture_Equals_To;

   function Is_All_Books_Placed return Boolean
   is
      subtype Book is Uint16_t range 144 .. 157;
   begin
      Trace_Log("is_all_books_placed");
      for item of All_World.Leader.Bag loop
         if item.Id in Book and then item.Count > 0 then
            return False;
         end if;
      end loop;
      return True;
   end Is_All_Books_Placed;

   procedure Play_Time_Machine_Movie (M1, M2 : Story_Movie_Type)
   is
   begin
      Trace_Log("play_time_machine_movie(m1 => " & Traces.Image(M1) & ", M2 => " & Traces.Image(M2) & ")");
      Stages.Current_Stage.Play_Story_Movies((M1, M2));
   end Play_Time_Machine_Movie;

   procedure Modify_Sex (Hero : Uint16_t; Sex : Sex_Type)
   is
      who : Hero_Type renames All_World.Heroes(Hero);
   begin
      Trace_Log("modify_sex(sex => " & Sex'Img & ")");
      who.Sex := Sex;
   end Modify_Sex;

   procedure Shopping 
   is
   begin
      Trace_Log("shopping");
      Stages.Current_Stage.Shopping;
   end Shopping;

   procedure Open_Random_Shop_System
   is
   begin
      Trace_Log("open_random_shop_system");
   end Open_Random_Shop_System;

   procedure Change_Shop_Position
   is
      Inns : constant Uint16_Array := (
         Resources.Heluo_Inn,    
         Resources.Youjian_Inn,  
         Resources.Yuelai_Inn,   
         Resources.Longmen_Inn,  
         Resources.Gaosheng_Inn); 
      Story_Id : constant Uint16_Array := (16, 14, 20, 16, 9);
      Story_Position : constant Point_Array := (
         (28, 30), (28, 16), (40, 18), (46, 34), (36, 30));
      Next : Natural := Random_Natural(Rnd_Generator, Inns'First, Inns'Last);
   begin
      Trace_Log("change_shop_position");
      for i in Inns'Range loop
         declare
            stories : Stories_Pointer := All_Scene_Stories(Inns(i));
            story : Story_Type renames stories(Story_Id(i));
         begin
            if i = Next then
               story.Visible := YES;
               story.Movie_Frame := (others => 8256);
               story.Scripts := (sc_Space => 938, others => No_Script); 
            else
               story.Visible := NO;
               story.Movie_Frame := (others => No_Picture);
               story.Scripts := (others => No_Script); 
            end if;
         end;
      end loop;
   end Change_Shop_Position;

   procedure Scroll_Map_To (Where : Point_Type)
   is
   begin
      Trace_Log("scroll_map_to(where => " & Traces.Image(Where) & ")");
      Stages.Current_Stage.Scroll_To_Canvas_Center(Where);
   end Scroll_Map_To;

   procedure Play_Sound (Sound : Uint16_t)
   is
   begin
      Trace_Log("play_sound(sound =>" & Sound'Img & ")");
      Music.Play_Effect_Sound(Sound);
   end Play_Sound;

   function Where_Is_The_Shop (Where : in out Point_Type) return Uint16_t
   is
      Inns : constant Uint16_Array := (
         Resources.Heluo_Inn, 
         Resources.Youjian_Inn,
         Resources.Yuelai_Inn,
         Resources.Longmen_Inn, 
         Resources.Gaosheng_Inn);
      Story_Id : constant Uint16_Array := (16, 14, 20, 16, 9);
      inn     : Uint16_t;
      stories : Stories_Pointer;
   begin
      for i in Inns'Range loop
         inn := Inns(i);
         stories := All_Scene_Stories(inn);
         declare
            story : Story_Type renames All_Scene_Stories(inn)(Story_Id(i));
         begin
            if story.Scripts(sc_Space) in 937 | Resources.Shopping_Script then
               if story.Visible = YES then
                  Where := story.Position;
                  return inn;
               end if;
            end if;
         end;
      end loop;
      return No_Scene;
   end Where_Is_The_Shop;

end Legend.Story_Engine;
